
#include "logger.h"

#include <stdio.h>
#include <unistd.h>
#include <stdarg.h>
#include <time.h>

static int log_debug_level = LOG_VERBOSE;
void (*log_assert_hook)(const char* expr, const char* func, int line);

static long get_ms(void)
{
    struct timespec ts;

    clock_gettime(CLOCK_BOOTTIME, &ts);
    return (long) (ts.tv_nsec) / ((1000) * (1000));
}

static const char *get_time(void)
{
    static char cur_system_time[24] = { 0 };

    time_t cur_t;
    struct tm cur_tm;

    time(&cur_t);
    localtime_r(&cur_t, &cur_tm);

    strftime(cur_system_time, sizeof(cur_system_time), "%Y-%m-%d %T", &cur_tm);

    return cur_system_time;
}

static const char *level_string(int level)
{
    const char *str[] = { "[F]", "[E]", "[W]", "[I]", "[D]", "[V]"};
    return str[level];
}

static void log_output(char *buffer)
{
    fprintf(LOG_OUTPUT, "%s", buffer);
}

void log_set_debug_level(int level)
{
    log_debug_level = level;
}

void log_set_assert_hook(void (*hook)(const char* expr, const char* func, int line))
{
    log_assert_hook = hook;
}

void log_debug(int level, const char *tag, const char *file,
               const char *func, int line, const char *fmt, ...)
{
    char buffer[LOG_BUF_SIZE];
    int size = 0;

    if (!((level <= LOG_VERBOSE) && (level <= log_debug_level)))
        return ;

    switch (level) {
    case LOG_FATAL:
        size += snprintf(buffer + size, LOG_BUF_SIZE - size, "\033[35m");
        size += snprintf(buffer + size, LOG_BUF_SIZE - size, "[%s.%03lu] ", get_time(), get_ms());
        size += snprintf(buffer + size, LOG_BUF_SIZE - size, "%s ", level_string(level));
        size += snprintf(buffer + size, LOG_BUF_SIZE - size, "%s(%s %s:%d): ", tag, file, func, line);
        size += snprintf(buffer + size, LOG_BUF_SIZE - size, "\033[35m");
        break;
    case LOG_ERROR:
        size += snprintf(buffer + size, LOG_BUF_SIZE - size, "\033[31m");
        size += snprintf(buffer + size, LOG_BUF_SIZE - size, "[%s.%03lu] ", get_time(), get_ms());
        size += snprintf(buffer + size, LOG_BUF_SIZE - size, "%s ", level_string(level));
        size += snprintf(buffer + size, LOG_BUF_SIZE - size, "%s(%s %s:%d): ", tag, file, func, line);
        size += snprintf(buffer + size, LOG_BUF_SIZE - size, "\033[31m");
        break;
    case LOG_WARN:
        size += snprintf(buffer + size, LOG_BUF_SIZE - size, "\033[33m");
        size += snprintf(buffer + size, LOG_BUF_SIZE - size, "[%s.%03lu] ", get_time(), get_ms());
        size += snprintf(buffer + size, LOG_BUF_SIZE - size, "%s ", level_string(level));
        size += snprintf(buffer + size, LOG_BUF_SIZE - size, "%s(%s %s:%d): ", tag, file, func, line);
        size += snprintf(buffer + size, LOG_BUF_SIZE - size, "\033[33m");
        break;
    default:
        size += snprintf(buffer + size, LOG_BUF_SIZE - size, "[%s.%03lu] ", get_time(), get_ms());
        size += snprintf(buffer + size, LOG_BUF_SIZE - size, "%s ", level_string(level));
        size += snprintf(buffer + size, LOG_BUF_SIZE - size, "%s(%s %s:%d): ", tag, file, func, line);
        break;
    }

    va_list var_args;

    va_start(var_args, fmt);
    size += vsnprintf(buffer + size, LOG_BUF_SIZE - size, fmt, var_args);
    va_end(var_args);

    if (level <= LOG_WARN)
        size += snprintf(buffer + size, LOG_BUF_SIZE - size, "\033[0m");

    log_output(buffer);
}
